package upm.pb4agent.team5.util;

import jade.core.AID;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import upm.pb4agent.ontology.Boat;
import upm.pb4agent.ontology.RealRow;
import upm.pb4agent.ontology.RegistrationForm;
import upm.pb4agent.ontology.Row;
import upm.pb4agent.team5.enumeration.ConfigProperty;
import upm.pb4agent.team5.enumeration.RowDirection;
import upm.pb4agent.team5.enumeration.RowStrength;

public class WatcherPhysics {
	enum Operation {
		ABSCISSAS,
		ORDINATES;		
	}
	
	enum RowerProperty {
		STRENGTH,
		RESISTANCE,
		AGILITY;
	}
	
	private static final boolean DEBUG = false;
	private RegistrationForm registrationForm = null;
	private Boat boat = null;
	
	private Map<String, List<Float>> rowersTiredness = null;
	private Map<String, List<Float>> rowersImpulse = null;
	private Map<String, List<Float>> rowersTimeRow = null;
	
	public WatcherPhysics() {
		rowersTiredness = new Hashtable<String, List<Float>>();
		rowersImpulse = new Hashtable<String, List<Float>>();
		rowersTimeRow = new Hashtable<String, List<Float>>();
	}

	public static void main(String[] args) throws InterruptedException {
		Row row = new Row();
		row.setBoat(new Boat());
		row.setDirection(RowDirection.FORWARD.ordinal());
		row.setStrength(RowStrength.STRONG.ordinal());
		
		String rowerName = "Rower51";
		WatcherPhysics watcherPhysics = new WatcherPhysics();
		
		while (true) {
			for (int i=0; i<4; i++) {
				row.setOarID(upm.pb4agent.team5.common.Boat.OAR_ID[i]); // changing oars

				RealRowWrapper realRowWrapper = watcherPhysics.realRow(row, rowerName);
				System.out.println("x=" + realRowWrapper.getRealRow().getX() + 
						", y=" + realRowWrapper.getRealRow().getY() + 
						", timeRow=" + realRowWrapper.getTimeRow());
				Thread.sleep(5000);
			}
		}
	}
	
	public void setRegistrationForm(RegistrationForm registrationForm) {
		this.registrationForm = registrationForm;
	}
	
	public void setBoat(Boat boat) {
		this.boat = boat;
	}
	
	public RealRowWrapper realRow(Row row, AID rowerAID) {
		return realRow(row, rowerAID.getLocalName());
	}

	RealRowWrapper realRow(Row row, String rowerName) {
		// TODO Validate against RegistrationForm and Boat
		float strength = getRowerProperty(rowerName, RowerProperty.STRENGTH);
		float agility = getRowerProperty(rowerName, RowerProperty.AGILITY); // TODO used in other part
		float resistance = getRowerProperty(rowerName, RowerProperty.RESISTANCE);

		// Initializing historical tiredness and impulse for the specified rower (in case is needed)
		initializeRowersTiredness(rowerName);
		initializeRowersImpulse(rowerName);
		initializeRowersTimeRow(rowerName);

		// Calculating impulse in t
		float impulse = calculateImpulse(row.getStrength());
		rowersImpulse.get(rowerName).add(impulse); // Adding impulse to historical
		
		// Calculating tiredness in t
		float tiredness = calculateTiredness(rowerName, rowersTiredness.get(rowerName).size(), resistance);
		rowersTiredness.get(rowerName).add(tiredness); // Adding tiredness to historical
		
		if (tiredness > strength)
			tiredness = strength;

		// Calculating oar_coefs
		float oarCoefX = calculateOarCoef(row.getOarID(), Operation.ABSCISSAS);
		float oarCoefY = calculateOarCoef(row.getOarID(), Operation.ORDINATES);
		
		// Calculating directionFactor
		int directionFactorX = calculateDirectionFactor(row.getOarID(), row.getDirection(), Operation.ABSCISSAS);
		int directionFactorY = calculateDirectionFactor(row.getOarID(), row.getDirection(), Operation.ORDINATES);

		// Calculating components (without rounding)
		float x = impulse * (strength - tiredness) * oarCoefX * directionFactorX;
		float y = impulse * (strength - tiredness) * oarCoefY * directionFactorY;

		// Calculating time_row
		float timeRow = calculateTimeRow(rowerName, rowersTimeRow.get(rowerName).size(), agility);
		rowersTimeRow.get(rowerName).add(timeRow); // Adding tiredness to historical

		if (DEBUG) {
			System.out.println("oarID=" + row.getOarID());
			System.out.println("direction=" + row.getDirection());
			System.out.println("strength=" + strength);
			System.out.println("agility=" + agility);
			System.out.println("resistance=" + resistance);
			System.out.println("impulse=" + impulse);
			System.out.println("tiredness=" + tiredness);
			System.out.println("oarCoefX=" + oarCoefX);
			System.out.println("oarCoefY=" + oarCoefY);
			System.out.println("x=" + x);
			System.out.println("y=" + y);
			System.out.println("timeRow=" + timeRow);
		}
		
		RealRow realRow = null;
		
		realRow = new RealRow();
		realRow.setX((int) Math.ceil(x)); 
		realRow.setY((int) Math.ceil(y)); 
		realRow.setOarID(row.getOarID());		
		
		return new RealRowWrapper(realRow, timeRow);
	}
	
	private float calculateImpulse(int rowStrength) {
		String key = "impulse_";
		
		if (rowStrength == RowStrength.STRONG.ordinal())
			key += RowStrength.STRONG.name().toLowerCase();
		else if (rowStrength == RowStrength.MEDIUM.ordinal())
			key += RowStrength.MEDIUM.name().toLowerCase();
		else 
			key += RowStrength.WEAK.name().toLowerCase();
		
		return Float.parseFloat(ConfigFile.getProperty(key));
	}
	
	private float calculateTiredness(String rowerName, int index, float resistance) {
		if (index == 0) 
			return 0;
		else 
			return calculateTiredness(rowerName, index - 1, resistance) + 
				rowersImpulse.get(rowerName).get(index - 1) * (1 - resistance);
	}
	
	private float calculateTimeRow(String rowerName, int index, float agility) {
		if (index == 0) {
			int rowSeconds = Integer.parseInt(
					ConfigFile.getProperty(ConfigProperty.row_seconds.name())
				); 
			return rowSeconds - agility;
		} else { 
			return calculateTimeRow(rowerName, index - 1, agility) + 
				rowersTiredness.get(rowerName).get(index - 1);
		} 
	}

	private float calculateOarCoef(String oarID, Operation operation) {
		String value = null; 
		
		// Calculating oar_coef
		if (operation.equals(Operation.ABSCISSAS)) { // x
			if (oarID.contains("1") || oarID.contains("2")) {
				value = ConfigFile.getProperty(
						ConfigProperty.front_oar_x_coef.name()
					);
			} else {
				value = ConfigFile.getProperty(
						ConfigProperty.back_oar_x_coef.name()
					);				
			}
		} else { // y
			if (oarID.contains("1") || oarID.contains("2")) {
				value = ConfigFile.getProperty(
						ConfigProperty.front_oar_y_coef.name()
					);
			} else {
				value = ConfigFile.getProperty(
						ConfigProperty.back_oar_y_coef.name()
					);				
			}			
		}
		
		return (value!=null)?Float.parseFloat(value):0;
	}

	private int calculateDirectionFactor(String oarID, int direction, Operation operation) {
		int directionFactor = 1; // Assuming RowDirection.FORWARD
		
		// Calculating oar_coef
		if (operation.equals(Operation.ABSCISSAS)) { // x 
			if (oarID.contains("1") || oarID.contains("3")) { // row left go right, row right go left
				if (direction == RowDirection.FORWARD.ordinal())
					directionFactor = 1;
				else
					directionFactor = -1;
			} else {
				if (direction == RowDirection.FORWARD.ordinal())
					directionFactor = -1;
				else
					directionFactor = 1;
			}
		} else { // y
			if (direction == RowDirection.FORWARD.ordinal())
				directionFactor = 1;
			else
				directionFactor = -1;
		}
		
		return directionFactor;
	}

	private float getRowerProperty(String rowerName, RowerProperty property) {
		char rowerId = rowerName.charAt(rowerName.length() - 1);
		
		String value = ConfigFile.getProperty(
				"rower" + rowerId + "_" + property.name().toLowerCase()
			);
		
		return (value!=null)?Float.parseFloat(value):0;
	}
	
	private void initializeRowersTiredness(String rowerName) {
		List<Float> tirednessHist = rowersTiredness.get(rowerName);
		
		if (tirednessHist == null) // The first time has not been initialized
			rowersTiredness.put(rowerName, new ArrayList<Float>());
	}
	
	private void initializeRowersImpulse(String rowerName) {
		List<Float> impulseHist = rowersImpulse.get(rowerName);
		
		if (impulseHist == null)
			rowersImpulse.put(rowerName, new ArrayList<Float>());
	}

	private void initializeRowersTimeRow(String rowerName) {
		List<Float> timeRowHist = rowersTimeRow.get(rowerName);
		
		if (timeRowHist == null)
			rowersTimeRow.put(rowerName, new ArrayList<Float>());
	}

}